/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package fss.client;

import fss.client.ui.FileListItem;
import fss.common.communication.DownloadRequest;
import fss.common.communication.PingRequest;
import fss.common.communication.QueryResult;
import fss.common.communication.UploadRequest;
import fss.common.communication.UploadResponse;
import fss.core.config.FssProperties;
import fss.core.logger.FssLogger;
import fss.remote.RemoteClient;
import fss.remote.RemoteService;
import fss.remote.util.RemoteFileService;
import fss.remote.util.RemoteFileServiceClient;
import fss.remote.util.RemoteFileServiceRegistry;
import fss.transfer.StatusType;
import fss.transfer.Transfer;
import fss.transfer.TransferId;
import fss.transfer.TransferManager;
import fss.transfer.TransferUtils;
import fss.util.FssUtils;
import fss.util.SerializableUtils;
import fss.util.rmi.RmiId;
import fss.util.rmi.RmiUtils;
import java.io.File;
import java.io.Serializable;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.logging.Logger;

/**
 *
 * @author hieu
 */
public class ClientUIModel
  extends Observable
  implements RemoteClient, RemoteFileServiceClient, Serializable
{
  /**
   * The data directory (includes the '/').
   */
  private static final String DATA_DIR = "/data";
  /**
   * The id.
   */
  private RmiId id = null;
  /**
   * Home directory for this client.
   */
  private String homedir = null;
  /**
   * Data directory for this client (i.e. the directory where files are stored).
   */
  private String datadir = null;
  /**
   * The transfer manager.
   */
  private TransferManager transferManager = null;

  private class UploadMonitor implements Runnable
  {
    TransferId id;
    private UploadMonitor(TransferId aId)
    {
      id = aId;
    }
    
    @Override
    public void run()
    {
      Transfer monitor = transferManager.getTransfer(id);
      while (monitor.getStatus() != StatusType.COMPLETED)
      {
        try
        {
          setChanged();
          ClientUIModelEvent event =
            new ClientUIModelEvent(
              ClientUIModelEvent.EventType.eUploadBlockProcessed);
          double progress = ((double)monitor.getOffset()) / monitor.getFilesize();
          int percentage = (int) (progress * 100);
          event.setTransferProgress(percentage);
          notifyObservers(event);
          Thread.sleep(150);
        }
        catch (Exception Ex)
        {
          //do nothing
        }
      }
      setChanged();
      ClientUIModelEvent event =
        new ClientUIModelEvent(
        ClientUIModelEvent.EventType.eUploadCompleted);
      notifyObservers(event);
    }
  }

  public ClientUIModel(ClientUI aView, RmiId aClientId)
  {
    this.id = aClientId;
    homedir = FssProperties.getInstance().getClientHomeDir(id.getName());
    datadir = homedir + DATA_DIR;
    FssUtils.createDirectory(homedir);
    FssUtils.createDirectory(datadir);
    transferManager = new TransferManager(datadir);
  }

  /**
   * Get the home directory for this client.
   * @return the homedir.
   */
  public String getHomedir()
  {
    return homedir;
  }

  /**
   * Get the data directory for this client.
   * @return the datadir.
   */
  public String getDatadir()
  {
    return datadir;
  }

  @Override
  public void update(Object observable, Object message) throws RemoteException
  {
    Logger logger = FssLogger.getInstance().getClientLogger(id.getName());
    /*
     * Ping Request
     */
    if (message instanceof PingRequest)
    {
      // do nothing, no need to clutter up log messages
    }
    /*
     * In this framework, the Transfer manager from a Remote File Service pushes Transfer
     * objects through the java Observer/Obserable framework to the client.
     */
    else if (message instanceof Transfer)
    {
      Transfer transfer = (Transfer) message;
      handleTransfer(transfer);
    }
    /*
     * For an upload, the client is the SOURCE (from TransferId), so the
     * client must check that it is the one who is expected to process
     * this upload.
     */
    else if (message instanceof UploadResponse)
    {
      UploadResponse response = (UploadResponse) message;
      if (response.getTransferId().getSourceId().equals(id))
      {
        logger.info("Received Upload Response from " + response.getSource() + " and will process.");
        UploadMonitor monitor = new UploadMonitor(response.getTransferId());
        Thread monitorThread = new Thread(monitor);
        transferManager.process(response);
        monitorThread.start();
      }
      else
      {
        logger.info("Ignoring Upload Response from " + response.getSource());
      }
    }
  }

  @Override
  public RmiId getId() throws RemoteException
  {
    return id;
  }

  @Override
  public void stop() throws RemoteException
  {
    //BLAH BLAH
  }

  @Override
  public void start() throws RemoteException
  {
    //BLAH BLAH
  }

  /**
   * Handle a download request transfer.
   * @param transfer the transfer.
   */
  private void handleTransfer(Transfer transfer)
  {
    Logger logger = FssLogger.getInstance().getClientLogger(id.getName());
    // only process if this client is the intended destination
    if (transfer.getId().getDestinationId().equals(id))
    {
      TransferUtils.handleTransfer(logger, datadir, transfer);

      if (transfer.getStatus() == StatusType.COMPLETED)
      {
        setChanged();
        ClientUIModelEvent event =
          new ClientUIModelEvent(
            ClientUIModelEvent.EventType.eDownloadCompleted);
        notifyObservers(event);
      }
      else if (transfer.getStatus() == StatusType.INPROGRESS)
      {
        setChanged();
        ClientUIModelEvent event =
          new ClientUIModelEvent(
            ClientUIModelEvent.EventType.eDownloadBlockProcessed);
        double progress = ((double) transfer.getOffset()) / transfer.getFilesize();
        int percentage = (int) (progress * 100);
        event.setTransferProgress(percentage);
        notifyObservers(event);
      }
    }
  }

  @Override
  public void resume(TransferId transferId) throws RemoteException
  {
    Logger logger = FssLogger.getInstance().getClientLogger(id.getName());

    boolean found = false;
    List<Transfer> incomplete = TransferUtils.getIncompleteTransferList(datadir);
    for (Transfer transfer : incomplete)
    {
      if (transfer.getId().equals(transferId))
      {
        logger.info("Incomplete Transfer found: " + transfer);
        DownloadRequest request = new DownloadRequest(id, transferId);
        request.setOffset(transfer.getOffset()); // set the offset
        download(request);
        found = true;
        break; // no need to continue looking through incomplete transfer list
      }
    }
    if (!found)
    {
      throw new RemoteException("Cannot find incomplete transfer for Transfer ID: " + transferId);
    }
  }

  @Override
  public void upload(UploadRequest request) throws RemoteException
  {
    // the *service* is the destination since this is an upload
    RmiId serviceId = request.getTransferId().getDestinationId();
    RemoteFileService service = (RemoteFileService) RmiUtils.getRemoteObject(serviceId);
    service.register(this);
    UploadMonitor monitor = new UploadMonitor(request.getTransferId());
    Thread monitorUploadThread = new Thread(monitor);
    try
    {
      monitorUploadThread.start();
    }
    catch (Exception aEx)
    {
      
    }
    service.processRequest(request);
  }

  @Override
  public void download(DownloadRequest request) throws RemoteException
  {
    // the *service* is the source since this is a download
    RmiId serviceId = request.getTransferId().getSourceId();
    RemoteService service = (RemoteService) RmiUtils.getRemoteObject(serviceId);
    if (service == null)
    {
      setChanged();
      ClientUIModelEvent event =
        new ClientUIModelEvent(ClientUIModelEvent.EventType.eLostService);
      notifyObservers(event);
      throw new RemoteException("Cannot connect to the RemoteService " + serviceId);

    }
    service.processRequest(request);
  }

  @Override
  public List<QueryResult> query(RmiId registryId, String param) throws RemoteException
  {
    RemoteFileServiceRegistry registry = (RemoteFileServiceRegistry) RmiUtils.getRemoteObject(registryId);
    if (registry == null)
    {
      throw new RemoteException("Cannot connect to the Remote File Service Registry " + registryId);
    }
    List<QueryResult> results = null;
    results = registry.query(param);
    return results;
  }

  public List<Transfer> getIncompleteTransferList()
  {
    Logger logger = FssLogger.getInstance().getClientLogger(id.getName());
    ArrayList<Transfer> tReturn = new ArrayList<Transfer>();
    File dir = new File(datadir);
    File[] contents = dir.listFiles();
    for (File file : contents)
    {
      if (file.getName().endsWith(".incomplete"))
      {
        String tPersistedFileName = file.getAbsolutePath();
        Object tDeserialized = SerializableUtils.deserializeObject(tPersistedFileName);
        if (tDeserialized instanceof Transfer)
        {
          tReturn.add((Transfer) tDeserialized);
          logger.info("Found incomplete transfer for file " + file.getName());
        }
      }
    }
    return tReturn;
  }

  /**
   * Get a list of all file(s) on the file service.
   * @return a list of file(s) or an empty list.
   */
  public ArrayList<FileListItem> getLocalFileListItems()
  {
    ArrayList<FileListItem> result = new ArrayList<FileListItem>();
    File dir = new File(datadir);
    File[] contents = dir.listFiles();
    for (File file : contents)
    {
      if (!file.getName().endsWith(".incomplete") &&
          !file.getName().startsWith("."))
      {
        result.add(FileListItem.createCompleteFile(file.getName(), (int) file.length()));
      }
    }
    return result;
  }
}
